Keep the service key when resolving keyed services via service location#364
Merged
Conversation
…on (GH-2878)
When a generated method is forced onto the scoped-IServiceProvider "service
location" path — because a dependency injects IServiceProvider directly or has
an opaque lambda registration (e.g. the ones MS Graph adds) — every sibling
dependency in that method is resolved through GetServiceFromScopedContainerFrame.
That frame always emitted GetRequiredService<T>(provider) and dropped the key,
so keyed services either threw ("No service for type T") or resolved the wrong
registration. Keyed services and opaque registrations each worked in isolation,
but not together in the same handler.
Thread the service key through GetServiceFromScopedContainerFrame and emit
GetRequiredKeyedService<T>(provider, key) (key rendered via CodeFormatter) when
the descriptor is keyed. Both call sites — ServiceLocationPlan.CreateVariable
and ServiceCollectionServerVariableSource.useServiceProvider — now pass the key.
Non-keyed resolution is unchanged.
Adds CodegenTests reproducing the bug end-to-end (generated code + compiled,
runtime resolution) the way a Wolverine handler is generated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Fixes the root cause of JasperFx/wolverine#2878: keyed services and opaque (lambda-factory) registrations each work in isolation, but not together in the same handler.
When anything in a generated method forces the scoped-
IServiceProvider"service location" path — a dependency that injectsIServiceProviderdirectly, or an opaque lambda registration like the ones the MS Graph SDK adds (AddScoped<IFoo>(_ => …)) — every sibling dependency in that method is resolved throughGetServiceFromScopedContainerFrame. That frame always emitted:and dropped the key, so a keyed service dragged onto that path either threw (
No service for type T has been registered) or resolved the wrong registration. The stringGetRequiredKeyedServiceappeared nowhere in the generated output. The inline[FromKeyedServices]constructor path was fine — only the service-location frame discarded the key.Diagnosis credit to @BlackChepo in the issue thread.
Fix
Thread the service key through
GetServiceFromScopedContainerFrameand emit the keyed call when the descriptor is keyed:Both call sites now pass the key:
ServiceLocationPlan.CreateVariable(keyed opaque registrations)ServiceCollectionServerVariableSource.useServiceProvider(keyed services dragged onto location by a sibling opaque dependency)Non-keyed resolution is byte-for-byte unchanged (the new constructor parameter defaults to
null).Tests
CodegenTests/Services/keyed_service_location.csreproduces the bug the way Wolverine generates a handler — aMethodCallto a handler whose parameters are a[FromKeyedServices("blue")] IWidgetplus an opaqueIScopedLambdasibling that forces service location:GetRequiredKeyedService<IWidget>(provider, "blue")for both the keyed-concrete and keyed-opaque registrations;No service for type IWidgetbefore the fix).All 3 fail before the fix and pass after. Full suite green locally: CodegenTests 346/346, CoreTests 439/439 (net9.0).
🤖 Generated with Claude Code